package org.kazuhiko.rpg.core;

import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.toRadians;
import static org.lwjgl.opengl.ARBDepthClamp.GL_DEPTH_CLAMP;
import static org.lwjgl.opengl.GL11.GL_MODELVIEW;
import static org.lwjgl.opengl.GL11.GL_PROJECTION;
import static org.lwjgl.opengl.GL11.GL_TRANSFORM_BIT;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glLoadIdentity;
import static org.lwjgl.opengl.GL11.glMatrixMode;
import static org.lwjgl.opengl.GL11.glOrtho;
import static org.lwjgl.opengl.GL11.glPopAttrib;
import static org.lwjgl.opengl.GL11.glPushAttrib;
import static org.lwjgl.opengl.GL11.glRotatef;
import static org.lwjgl.opengl.GL11.glTranslatef;

import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Vector3f;

public class Camera
{	
	private final float zNear;
	private final float zFar;
	
	private float x;
	private float y;
	private float z;
	private float pitch;
	private float yaw;
	private float roll;
	private float fov;
	private float aspectRatio;
	
	private Drawable followObject;
	
	private boolean isFollowingObject;
	
	private Vector3f followDistance;
	
	public Camera()
	{
		fov = 90;
		aspectRatio = 1;
		
		isFollowingObject = false;
		followDistance = new Vector3f( 0, 0, 0 );
		
		zNear = 0.4f;
		zFar = 100;
	}
	
	public Camera( float aspectRatio, float x, float y, float z )
	{
		this();
		this.aspectRatio = aspectRatio;
		this.x = x;
		this.y = y;
		this.z = z;
	}
	
	public void processMouse()
	{
		processMouse( 0 );
	}
	
	public void processMouse( int mouseSpeed )
	{
		processMouse( mouseSpeed, 90, -90 );
	}
	
	public void processMouse( float mouseSpeed, float maxLookUp, float maxLookDown )
	{	
		float mouseDX = Mouse.getDX() * mouseSpeed * 0.16f;
		float mouseDY = Mouse.getDY() * mouseSpeed * 0.16f;
		
		if( Mouse.isGrabbed() )
		{			
			if( yaw + mouseDX >= 360 )
			{
				yaw = yaw + mouseDX - 360;
			}
			else if ( yaw + mouseDX < 0 )
			{
				yaw = 360 - yaw + mouseDX;
			}
			else
			{
				yaw += mouseDX;
			}
			
			if( pitch - mouseDY >= maxLookDown && pitch - mouseDY <= maxLookUp )
			{
				pitch += -mouseDY;
			}
			else if( pitch - mouseDY < maxLookDown )
			{
				pitch = maxLookDown;
			}
			else if( pitch - mouseDY > maxLookUp )
			{
				pitch = maxLookUp;
			}
		}
	}
	
	public void processKeyboard( float delta, float speedX, float speedY, float speedZ )
	{
		if( delta <= 0 )
		{
			//throw new IllegalArgumentException( "delta " + delta + " is 0 or smaller than 0" );
			delta = 1;
		}
		
		boolean keyUp = Keyboard.isKeyDown( Keyboard.KEY_W );
		boolean keyDown = Keyboard.isKeyDown( Keyboard.KEY_S );
		boolean keyLeft = Keyboard.isKeyDown( Keyboard.KEY_A );
		boolean keyRight = Keyboard.isKeyDown( Keyboard.KEY_D );
		boolean flyUp = Keyboard.isKeyDown( Keyboard.KEY_SPACE );
		boolean flyDown = Keyboard.isKeyDown( Keyboard.KEY_LSHIFT );
		
		if( keyUp && keyRight && !keyLeft && !keyDown )
		{
			moveFromLook( speedX * delta * 0.003f, 0, -speedZ * delta * 0.003f );
		}
		if( keyUp && keyLeft && !keyRight && !keyDown )
		{
			moveFromLook( -speedX * delta * 0.003f, 0, -speedZ * delta * 0.003f );
		}
		if( keyUp && !keyLeft && !keyRight && !keyDown )
		{
			moveFromLook( 0, 0, -speedZ * delta * 0.003f );
		}
		
		if( keyDown && keyLeft && !keyRight && !keyUp )
		{
			moveFromLook( -speedX * delta * 0.003f, 0, speedZ * delta * 0.003f );
		}
		if( keyDown && keyRight && !keyLeft && !keyUp )
		{
			moveFromLook( speedX * delta * 0.003f, 0, speedZ * delta * 0.003f );
		}
		if( keyDown && !keyUp && !keyLeft && !keyRight )
		{
			moveFromLook( 0, 0, speedZ * delta * 0.003f );
		}
		
		if( keyLeft && !keyRight && !keyDown && !keyUp )
		{
			moveFromLook( -speedX * delta * 0.003f, 0, 0 );
		}
		
		if( keyRight && !keyLeft && !keyUp && !keyDown )
		{
			moveFromLook( speedX * delta * 0.003f, 0, 0 );
		}
		
		if( flyUp && !flyDown )
		{
			y += speedY * delta * 0.003f;
		}
		
		if( flyDown && !flyUp )
		{
			y -= speedY * delta * 0.003f;
		}
	}
	
	public void moveFromLook( float dx, float dy, float dz )
	{
		if( !isFollowingObject )
		{
			this.z += dx * ( float ) cos( toRadians( yaw - 90 ) ) + dz * cos ( toRadians( yaw ) );
			this.x -= dx * ( float ) sin( toRadians( yaw - 90 ) ) + dz * sin( toRadians( yaw ) );
			this.y += dy * ( float ) sin( toRadians( pitch - 90 ) ) + dz * sin( toRadians( pitch ) );
		}
	}
	
	public void setPosition( float x, float y, float z )
	{
		this.x = x;
		this.y = y;
		this.z = z;
	}
	
	public void setRotation( float pitch, float yaw, float roll )
	{
		this.pitch = pitch;
		this.yaw = yaw;
		this.roll = roll;
	}
	
	public void followObject( Drawable followObject )
	{
		followObject( followObject, followDistance );
	}
	
	public void followObject( Drawable followObject, Vector3f followDistance )
	{
		isFollowingObject = true;
		setPosition( followObject.getPosition().x, followObject.getPosition().y,  followObject.getPosition().z );
		//setRotation( followObject.getRotation().x, followObject.getRotation().y, followObject.getRotation().z );
		this.followObject = followObject;
		this.followDistance = followDistance;
	}
	
	public void tick()
	{
		if( isFollowingObject )
		{
			setRotation( 45, 0, 0 );
			setPosition( followObject.getPosition().x, followObject.getPosition().y + 5, followObject.getPosition().z +5 );
		}
	}
	
	public Vector3f getFollowDistance()
	{
		return followDistance;
	}
	
	public void setFollowDistance( float x, float y, float z )
	{
		setFollowDistance( new Vector3f( x, y, z ) );
	}
	
	public void setFollowDistance( Vector3f followDistance )
	{
		this.followDistance = followDistance;
	}
	
	public Drawable getFollowObject()
	{
		return followObject;
	}
	
	public boolean isFollowing()
	{
		return isFollowingObject;
	}
	
	public void stopFollow()
	{
		isFollowingObject = false;
		followObject = null;
		setRotation( 0, 0, 0 );
	}
	
	public void pauseFollow()
	{
		isFollowingObject = !isFollowingObject;
		setRotation( 0, 0, 0 );
	}
	
	public float x()
	{
		return x;
	}
	
	public float y()
	{
		return y;
	}
	
	public float z()
	{
		return z;
	}
	
	public float pitch()
	{
		return pitch;
	}
	
	public float yaw()
	{
		return yaw;
	}
	
	public float roll()
	{
		return roll;
	}
	
	public float fieldOfView()
	{
		return fov;
	}
	
	public void setFieldOfView( float fov )
	{
		this.fov = fov;
	}
	
	public void setAspectRatio( float aspectRatio )
	{
		if( aspectRatio <= 0 )
		{
			throw new IllegalArgumentException( "aspectRatio " + aspectRatio + " is 0 or less" );
		}
		
		this.aspectRatio = aspectRatio;
	}
	
	public float aspectRatio()
	{
		return aspectRatio;
	}
	
	public float nearClippingPane()
	{
		return zNear;
	}
	
	public float farClippingPane()
	{
		return zFar;
	}
	
	public void applyOrthographicMatrix()
	{
		glPushAttrib( GL_TRANSFORM_BIT );
		glMatrixMode( GL_PROJECTION );
		glLoadIdentity();
		glOrtho( -aspectRatio, aspectRatio, -1, 1, 0, zFar );
		glPopAttrib();
	}
	
	public void applyOptimalStates()
	{
		if( GLContext.getCapabilities().GL_ARB_depth_clamp )
		{
			glEnable( GL_DEPTH_CLAMP );
		}
	}
	
	public void applyPerspectiveMatrix()
	{
		glPushAttrib( GL_TRANSFORM_BIT );
		glMatrixMode( GL_PROJECTION );
		glLoadIdentity();
		GLU.gluPerspective( fov, aspectRatio, zNear, zFar);
		glPopAttrib();
	}
	
	public void applyTranslations()
	{
		glPushAttrib( GL_TRANSFORM_BIT );
		glMatrixMode( GL_MODELVIEW );
		glRotatef( pitch, 1, 0, 0 );
		glRotatef( yaw, 0, 1, 0 );
		glRotatef( roll, 0, 0, 1 );
		glTranslatef( -x, -y, -z );
		glPopAttrib();
	}
}
